/*
**  (C) by Rael Delcon (rael.delcon@gmail.com)
**
** Permission to use, copy, modify and distribute this code and
** its documentation for any purpose is hereby granted without
** fee, provided that the above copyright notice, or equivalent
** attribution acknowledgement, appears in all copies and
** supporting documentation.
**
** Copyright holder makes no representations about the suitability
** of this software for any purpose. It is provided "as is" without
** express or implied warranty.
*/

#include "m3d.h"

#define txtline_len 128
static char txtline[txtline_len];

m3Dmesh *m3Dnew()
{
  m3Dmesh *m = NULL;
  
  if ((m = malloc(sizeof(m3Dmesh)))) {
    m->vnum = 0; m->vsize = 0;
    m->fnum = 0; m->fsize = 0;
    m->verts = NULL;
    m->faces = NULL; 
  }
  return m;
}

m3Dmesh *m3Dfree(m3Dmesh *m)
{
  if (m) {
    free(m->verts);
    free(m->faces);
    free(m);
  }
  return NULL;
}

char *eatspace(char *s)
{
  while (isspace(*s)) s++;
  return s;
}

static char *getcoord(char *s, float *coord, float defval)
{
   while (*s && !isdigit(*s) && *s != '-') s++;
   if (sscanf(s,"%g",coord) < 1)  *coord = defval;
   while (isdigit(*s) || *s == '.' || *s == '-') s++;
   return s;
}

static char *getvindex(char *s, unsigned short *val, unsigned short defval)
{
   while (*s && !isdigit(*s) && *s != '-') s++;
   if (sscanf(s,"%hu",val) < 1) *val = defval;
   else *val -= 1;     
   while (*s && !isspace(*s)) s++;
   return s;
}

static char *getop(char *s, unsigned short *op)
{
  *op = 0;
  while (*s && isspace(*s)) s++;
  switch (*s) {
    case '#' : *op = *s++;
               break;
    
    case 'v' : 
    case 'f' : if (s[1] == ' ') *op = *s++; 
               while (*s && isspace(*s)) s++;
    default  : break;
  }
  return s;
}


m3Dmesh *m3DloadObj(FILE *fin)
{
  m3Dmesh *m = NULL;
  float x,y,z;
  unsigned short v1, v2, v3, v4;
  unsigned short op;
  char *s;

  if (!(m = m3Dnew())) return NULL;
    
  while (fgets(txtline,txtline_len,fin)) {
    s = getop(txtline, &op);
    switch (op) {
      case '#': break;
      
      #define get(w_,s_,v_,d_) get##w_(s_, &v_, d_); if (v_ == d_) goto err;
      #define getopt(w_,s_,v_,d_) get##w_(s_, &v_, d_); 
      
      case 'v': if (m->vnum >= m3Dmax) goto err;
  
                s = get(coord, s, x, m3DnoCoord);
                s = get(coord, s, y, m3DnoCoord);
                s = get(coord, s, z, m3DnoCoord);
                
                m3DaddVert(m,x,y,z);
                break;
      
      case 'f': if (m->fnum >= m3Dmax) goto err;

                s = get(vindex,    s, v1, m3Dnil);
                s = get(vindex,    s, v2, m3Dnil);
                s = get(vindex,    s, v3, m3Dnil);
                s = getopt(vindex, s, v4, m3Dnil);

                m3DaddFace(m, v1, v2, v3);
                if (v4 != m3Dnil) 
                  m3DaddFace(m, v1, v3, v4);
                break;
    }
  }
  
  return m;
  
err:
  free(m); return NULL;
}

int m3DsaveRaw(FILE *fout, m3Dmesh *m)
{
  unsigned short k;
  
  for (k = 0; k < m->vnum; k++) {
    fprintf(fout,"[%3u] v <%f %f %f> %u\n", k, m->verts[k].x,m->verts[k].y, m->verts[k].z,m->verts[k].f);
  }
  
  for (k = 0; k < m->fnum; k++) {
    fprintf(fout,"[%3u] f (%u %u %u) %u %u %u \n", k,
      m->faces[k].verts[0],m->faces[k].verts[1],m->faces[k].verts[2],
      m->faces[k].next[0], m->faces[k].next[1], m->faces[k].next[2]);
  }
  return 0; 
}

int m3DsaveObj(FILE *fout, m3Dmesh *m)
{
  unsigned short k;
  
  fprintf (fout,"# Vertices: %u Faces: %u Edges: %u\n",m3DnumVerts(m),m3DnumFaces(m),m3DnumEdges(m));
  for (k = 0; k < m->vnum; k++) {
    fprintf(fout,"v %f %f %f\n",m->verts[k].x,m->verts[k].y, m->verts[k].z);
  }
  
  for (k = 0; k < m->fnum; k++) {
    fprintf(fout,"f %u %u %u\n",
     m->faces[k].verts[0]+1,m->faces[k].verts[1]+1,m->faces[k].verts[2]+1);
  }
  return 0; 
}

int m3DsaveWtri(FILE *fout, m3Dmesh *m)
{
  unsigned short k,j;
  
  for (k = 0; k < m->vnum; k++) {
    fprintf(fout,"v %f %f %f\n",m->verts[k].x,m->verts[k].y, m->verts[k].z);
  }
  
  for (k = 0; k < m->fnum; k++) {
    fprintf(fout,"f %u %u %u",m->faces[k].verts[0]+1,m->faces[k].verts[1]+1,m->faces[k].verts[2]+1);
    j = m3DadjFace(m, k, 0);  if (j == m3Dnil) j = 0; else j++;
    fprintf(fout," %u",j);
    j = m3DadjFace(m, k, 1);  if (j == m3Dnil) j = 0; else j++;
    fprintf(fout," %u",j);
    j = m3DadjFace(m, k, 2);  if (j == m3Dnil) j = 0; else j++;
    fprintf(fout," %u\n",j);
  }
  return 0; 
}

m3Dindex m3DaddVert(m3Dmesh *m, float x, float y, float z)
{
  m3Dpoint *v;
  
  if (m->vnum >= m->vsize) {
    m->vsize += 3;
    v = realloc(m->verts, m->vsize * sizeof(m3Dpoint));
    if (!v) { m->vsize -= 3; return m3Dnil; }
    m->verts = v;
  }
  
  v = &m->verts[m->vnum];
  v->x = x; v->y = y ; v->z = z;
  v->mrk = 0; v->f = m3Dnil;
  return m->vnum++;
}

m3Dindex m3DaddFace(m3Dmesh *m, m3Dindex v0, m3Dindex v1, m3Dindex v2)
{
  m3Dface *f;
  
  if (   v0 >= m->vnum || v1 >= m->vnum || v2 >= m->vnum
      || v0 == v1 || v1 == v2 || v2 == v0 )
    return m3Dnil;
  
  if (m->fnum >= m->fsize) {
    m->fsize += 4;
    f = realloc(m->faces, m->fsize * sizeof(m3Dface));
    if (!f) { m->fsize -= 4; return m3Dnil; }
    m->faces = f;
  }
  
  f = &m->faces[m->fnum];
  
  f->verts[0] = v0; f->verts[1] = v1; f->verts[2] = v2;
  /* connect lists */
  f->next[0] = m->verts[v0].f; m->verts[v0].f = m->fnum;  
  f->next[1] = m->verts[v1].f; m->verts[v1].f = m->fnum;  
  f->next[2] = m->verts[v2].f; m->verts[v2].f = m->fnum;  
  f->mrk = 0; 
  return m->fnum++;
}

m3Dindex m3DnextFaceOn(m3Dmesh *m, m3Dindex v, m3Dindex cur)
{
  if (v >= m->vnum) return m3Dnil;
  
  if (cur == m3Dnil) return m->verts[v].f;
  if (m->faces[cur].verts[0] == v) return m->faces[cur].next[0]; 
  if (m->faces[cur].verts[1] == v) return m->faces[cur].next[1]; 
  if (m->faces[cur].verts[2] == v) return m->faces[cur].next[2]; 
  return m3Dnil;  
}
 
m3Dindex m3DadjFace(m3Dmesh *m, m3Dindex f, m3Dindex side)
{
  m3Dindex a, b; /* edge vertices */
  m3Dindex k;
  
  if (f >= m->fnum  || side >2) return m3Dnil;
  
  a = m->faces[f].verts[side];
  b = m->faces[f].verts[(side+1) % 3];
  
  for (k = m3DnextFaceOn(m,a,m3Dfirst); k != m3Dnil; k = m3DnextFaceOn(m,a, k)) {
    if ((k != f) && (   (m->faces[k].verts[0] == b)
                     || (m->faces[k].verts[1] == b)
                     || (m->faces[k].verts[2] == b)))  
      break;
  }
  
  return k;
}

void m3DmarkFace(m3Dmesh *m, m3Dindex f, short mrk)
{
  if (f == m3Dall) {
    for (f=0; f< m->fnum; f++) {
      m->faces[f].mrk = mrk;  
    }
  }
  else if (f < m->fnum)
    m->faces[f].mrk = mrk;
}

void m3DmarkVert(m3Dmesh *m, m3Dindex v, short mrk)
{
  if (v == m3Dall) {
    for (v=0; v < m->vnum; v++) {
      m->verts[v].mrk = mrk;  
    }
  }
  else if (v < m->vnum)
    m->verts[v].mrk = mrk;
}

int m3DnumEdges(m3Dmesh *m)
{
  m3Dindex i,j;
  int nedges = 0;
  m3DmarkFace(m, m3Dall, 3);
  
  for (i=0; i < m3DnumFaces(m); i++) {
    nedges += m->faces[i].mrk;
    m->faces[i].mrk = 0;
    j = m3DadjFace(m, i, 0);  if (j != m3Dnil) m->faces[j].mrk--; 
    j = m3DadjFace(m, i, 1);  if (j != m3Dnil) m->faces[j].mrk--;
    j = m3DadjFace(m, i, 2);  if (j != m3Dnil) m->faces[j].mrk--;
  }
  return nedges;
}
